package main

import (
	"encoding/binary"
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

const (
	NTP_HEADER_SIZE  = 48
	NTP_RECV_TS      = 32
	NTP_TRAN_TS      = 40
	NTP_HEADER_V3REQ = 0X1B
)

var (
	ntp_servers string = `ntp1.aliyun.com
	ntp.neu6.edu.cn
1.cn.pool.ntp.org
2.cn.pool.ntp.org
3.cn.pool.ntp.org
0.cn.pool.ntp.org
cn.pool.ntp.org
tw.pool.ntp.org
0.tw.pool.ntp.org
1.tw.pool.ntp.org
2.tw.pool.ntp.org
3.tw.pool.ntp.org
	`
)
var (
	t0, t9     time.Time
	ver, build string
)

func main() {
	v := flag.Bool("v", false, "查看版本号")
	m := flag.Int("m", 10, "每隔几分钟同步一次时钟")
	n := flag.Int("n", 1, "最多同步次数，小于等于0表示无限次")
	flag.Parse()
	if *v {
		fmt.Println("ntpc", ver, build)
		os.Exit(0)
	}
	fmt.Println("start ntpc", ver, build, time.Now().Format(time.RFC3339))
	t0, _ = time.ParseInLocation("2006-01-02 15:04:05", "2021-09-22 15:00:00", time.Local)
	t9, _ = time.ParseInLocation("2006-01-02 15:04:05", "2051-09-22 15:00:00", time.Local)
	for i := 1; ; i++ {
		t2()
		if *n > 0 && i >= *n {
			break
		}
		time.Sleep(time.Minute * time.Duration(*m))
	}
}

//创建一个ver3的ntp请求报文
func NewNtpV3Req() []byte {
	req := make([]byte, NTP_HEADER_SIZE, NTP_HEADER_SIZE)
	req[0] = NTP_HEADER_V3REQ
	return req
}

//从ntp响应报文中提取服务器回复时戳ReceiveTimestamp,TransmitTimestamp
func ParseResTimestampUnix(ntpRes []byte) (recv int64, tran int64, ok bool) {
	if len(ntpRes) < 48 {
		ok = false
		return
	}
	t2 := binary.BigEndian.Uint32(ntpRes[32:])
	t3 := binary.BigEndian.Uint32(ntpRes[40:])
	t2 -= 2208988800
	t3 -= 2208988800
	return int64(t2), int64(t3), true
}

func t1() {
	conn, err := net.ListenUDP("udp", nil)
	if err != nil {
		panic(err)
	}
	go func() {
		buf := make([]byte, 1024)
		for {
			n, cli, err := conn.ReadFromUDP(buf)
			if err != nil {
				log.Println(err)
				continue
			}
			if n < NTP_HEADER_SIZE {
				continue
			}
			ts3, ts4, ok := ParseResTimestampUnix(buf)
			if ok {
				t3, t4 := time.Unix(ts3, 0), time.Unix(ts4, 0)
				log.Println(cli, t4.Sub(t3), t4)
			}
		}
	}()
	req := NewNtpV3Req()
	for _, addr := range strings.Split(ntp_servers, "\n") {
		uaddr, err := net.ResolveUDPAddr("udp", addr+":123")
		if err != nil {
			log.Println(addr, err)
		} else {
			conn.WriteToUDP(req, uaddr)
			time.Sleep(time.Second)
		}
	}
}

func t2() bool {
	conn, err := net.ListenUDP("udp", nil)
	if err != nil {
		panic(err)
	}
	defer conn.Close()
	req := NewNtpV3Req()
	buf := make([]byte, 1024)
	var t1, t2, t3, t4 time.Time
	for _, addr := range strings.Split(ntp_servers, "\n") {
		addr = strings.TrimSpace(addr)
		nms, err := net.LookupHost(addr)
		if err != nil {
			log.Println(addr, err)
			continue
		}
		for _, nm := range nms {
			// log.Println(addr, nm)
			uaddr, err := net.ResolveUDPAddr("udp", nm+":123")
			if err != nil {
				log.Println(addr, nm, err)
			} else {
				t1 = time.Now()
				conn.SetWriteDeadline(time.Now().Add(time.Second))
				conn.WriteToUDP(req, uaddr)
				conn.SetReadDeadline(time.Now().Add(time.Second * 2))
				n, cli, err := conn.ReadFromUDP(buf)
				t4 = time.Now()
				if err != nil {
					// log.Println(err)
					continue
				}
				if n < NTP_HEADER_SIZE {
					continue
				}
				if ts2, ts3, ok := ParseResTimestampUnix(buf); ok {
					t2, t3 = time.Unix(ts2, 0), time.Unix(ts3, 0)
					if t3.Before(t0) || t3.After(t9) {
						fmt.Println("InvalidTime", t3.Format(time.RFC3339))
						continue
					}
					d, t := t4.Sub(t1), t4.Sub(t3)
					fmt.Println(addr, nm, cli, d, t3.Sub(t2), t, t3.Format(time.RFC3339))
					if t > time.Second || -t > time.Second {
						fmt.Println("Modify clock", t, t4.Format(time.RFC3339))
						switch osn := runtime.GOOS; osn {
						case "windows":
							cmd := exec.Command("cmd.exe", "/c", "date", t3.Format("2006-01-02"), "&&", "time", t3.Format("15:04:05"))
							cmd.CombinedOutput()
							if ec := cmd.ProcessState.ExitCode(); ec != 0 {
								fmt.Println("Modify clock err", ec)
							} else {
								fmt.Println("Modify clock success", time.Now().Format(time.RFC3339))
							}
						case "linux":
							cmd := exec.Command("/bin/date", fmt.Sprintf(`--set=%s`, t3.Format("2006-01-02 15:04:05")))
							buf, err := cmd.CombinedOutput()
							if ec := cmd.ProcessState.ExitCode(); ec != 0 {
								fmt.Println("Modify clock err", ec, string(buf), err)
							} else {
								fmt.Println("Modify clock ok", time.Now().Format(time.RFC3339))
							}
						default:
							fmt.Println("NotSupported OS", osn)
						}
					}
					return true
				}
			}
		}
	}
	return false
}
